package SSF.Util.Random;

import java.util.*;

import cern.jet.random.*;
import cern.jet.random.engine.*;
import edu.cornell.lassp.houle.RngPack.*;

import com.renesys.raceway.SSF.*;

import com.renesys.raceway.DML.*;

public class RandomStream extends RandomElement {
                           
//--------------------------------------------------- EXCEPTIONS 

  /* Base class for all exceptions thrown by RandomStream 
   * construction -- for convenience, so that you can 
   * catch them all in one line if desired. 
   */
  public class StreamException extends Exception {
    public StreamException(String s) {
      super(s);
    }
  };

  /** Exception thrown if two stream names accidentally 
    * hash to the same 32-bit extract of their MD5 digests. 
    * The only deterministic fix for this is to pick a different 
    * strategy for generating stream names that doesn't cause  
    * a collision.
    */
  public class CollisionException extends StreamException {
    public CollisionException(String badname) {
      super("Collision in stream name hash function: "+
            badname+" vs "+RandomStream.this.getName());
    }
  }

  /** Exception thrown if entities from two different SSF 
    * timelines both request the same stream.  Since streams
    * are by default unsynchronized, this would cause all 
    * kinds of mischief.  
    */
  public class AlignmentException extends StreamException {
    public AlignmentException() {
      super("Stream requestors not coaligned");
    }
  }

  /** Exception thrown when the user names an unknown 
    * random number generator.
    */
  public class NamingException extends StreamException {
    public NamingException(String name) {
      super("Unknown RNG name: "+name);
    }
  }

  /** Exception thrown when the user names an unknown 
    * random distribution.
    */
  public class DistributionException extends StreamException {
    public DistributionException(String name) {
      super("Unknown random distribution name: "+name);
    }
  }

//-------------------------------------------- STREAM INSTANCE INFO

  private RandomElement generator;

 // public double raw() { return (generator==null?0.:generator.raw()); }

  public double raw() { return generator.raw(); }

  public double nextDouble() { return generator.raw(); }

  private Object alignment;

  /** Return the SSF alignment of all entities sharing this stream.  */
  public final Object getAlignment() { return alignment; };

  private String name; 

  /** Return the so-called "universal name" of this stream, consisting of 
    * the generator name concatenated with "::" plus the user-supplied 
    * stream name.  The mapping between universal names and streams 
    * is guaranteed to be one-to-one. 
    */
  public final String getName() { return name; };

//---------------------------------------------- STREAM FACTORY 

  private static Hashtable streams;

  /** Obtain a reference to the globally unique random stream 
    * corresponding to the given generator name and stream name. 
    */
  public static synchronized RandomStream getStream(Entity requestor,
                                                    String generatorName, 
                                                    String streamName) 
    throws CollisionException, AlignmentException, NamingException  {

    String useName = generatorName+"::"+streamName; 

    // Hash the generator/stream name to a 16-byte MD5 digest.
     byte[] digest = MD5.encode(useName.getBytes());

    // Compute a 32-bit seed by arbitrarily selecting four of 16 bytes.
    // make sure it's nonnegative
     int dummy = 0;
     dummy |= ((Math.abs((int)digest[3]))<<24) & 0xff000000;
     dummy |= ((Math.abs((int)digest[6]))<<16) & 0xffff0000;
     dummy |= ((Math.abs((int)digest[9]))<<8)  & 0xffffff00;
     dummy |= Math.abs((int)digest[12]);

     Integer seed32 = new Integer(Math.abs(dummy));

    // Create the stream hashtable if necessary.  
     if (streams == null) streams = new Hashtable();

    // Has this seed been requested before?
     RandomStream existing = (RandomStream)streams.get(seed32);

    // If so, test for hash collision and proper alignment.
     if (existing!=null) {
       if (!useName.equals(existing.getName()))
         throw existing.new CollisionException(useName);
       else if (requestor.alignment() != existing.getAlignment()) 
         throw existing.new AlignmentException();
       else return existing;
     }

    // Otherwise, make a new stream and return it. 
    else {
      RandomStream newStream = 
        createStream(generatorName, seed32, useName, requestor.alignment());
      streams.put(seed32,newStream);

      return newStream;
    }
  }

//---------------------------------------------- STREAM CREATION (INTERNAL)

  protected RandomStream() {};

  private static RandomStream createStream(String generatorName,
                                            Integer seed32,
                                            String useName,
                                            Object useAlignment) 
       throws NamingException {

    RandomStream newStream = new RandomStream();
    newStream.alignment = useAlignment;
    newStream.name = useName;

    if ("MersenneTwister".equals(generatorName)) 
      newStream.generator = 
        new cern.jet.random.engine.MersenneTwister(seed32.intValue());
     
    else if ("Ranlux".equals(generatorName)) 
      newStream.generator = 
        new edu.cornell.lassp.houle.RngPack.Ranlux(seed32.intValue());

    else if ("Java".equals(generatorName)) {  // Example of roll-your-own-rng
      final Integer useSeed = seed32;
      newStream.generator = 
        new edu.cornell.lassp.houle.RngPack.RandomElement() {
          private boolean init = true;
          private java.util.Random rng;
          public double raw() {
            if (init) {
              rng = new java.util.Random();
              rng.setSeed(useSeed.intValue());
              init = false;
            }
            return rng.nextDouble();
          }
        };
    }

    else throw newStream.new NamingException(generatorName);

    return newStream;
  }

//---------------------------------------------- DISTRIBUTION FACTORY
// modified

  public static RandomDistribution
    getDistribution(String distname, RandomStream rng) 
      throws DistributionException {

      Configurable dist = null;
      if (distname != null) {
        
        if ("Beta".equals(distname)) 
          dist = rng.new cfgBeta(rng);
        
        if ("Binomial".equals(distname))        
          dist = rng.new cfgBinomial(rng);
        
        if ("BreitWigner".equals(distname)) 
          dist = rng.new cfgBreitWigner(rng);
        
        if ("BreitWignerMeanSquare".equals(distname))   
          dist = rng.new cfgBreitWignerMeanSquare(rng);
        
        if ("ChiSquare".equals(distname)) 
          dist = rng.new cfgChiSquare(rng);
        
        if ("Exponential".equals(distname)) 
          dist = rng.new cfgExponential(rng);
        
        if ("Gamma".equals(distname)) 
          dist = rng.new cfgGamma(rng);
        
        if ("HyperGeometric".equals(distname))  
          dist = rng.new cfgHyperGeometric(rng);
        
        if ("Hyperbolic".equals(distname))      
          dist = rng.new cfgHyperbolic(rng);
        
        if ("Logarithmic".equals(distname))     
          dist = rng.new cfgLogarithmic(rng);
        
        if ("NegativeBinomial".equals(distname))        
          dist = rng.new cfgNegativeBinomial(rng);
        
        if ("Normal".equals(distname))  
          dist = rng.new cfgNormal(rng);
        
        if ("Pareto".equals(distname))  
          dist = rng.new cfgPareto(rng);

        if ("ParetoII".equals(distname))
          dist = rng.new cfgParetoII(rng);
        
        if ("Poisson".equals(distname)) 
          dist = rng.new cfgPoisson(rng);
        
        if ("PoissonSlow".equals(distname))     
          dist = rng.new cfgPoissonSlow(rng);
        
        if ("Uniform".equals(distname))         
          dist = rng.new cfgUniform(rng);
        
        if ("VonMises".equals(distname))        
          dist = rng.new cfgVonMises(rng);

        if ("Weibull".equals(distname))
          dist = rng.new cfgWeibull(rng);

        if ("Wald".equals(distname))
          dist = rng.new cfgWald(rng);
        
        if ("Zeta".equals(distname))    
          dist = rng.new cfgZeta(rng);
      }
      if (dist == null) 
        throw rng.new DistributionException(distname);

      return new RandomDistribution((AbstractDistribution)dist);
    }
  
  public static RandomDistribution
    getDistribution(Configuration cfg, RandomStream rng) 
      throws DistributionException {
      String distname;
      try {     
        distname = (String)cfg.findSingle("name");
      } catch (configException cfgex) {
        distname = null;
      }
      RandomDistribution rdist = getDistribution(distname,rng);
       
      try {
        ((Configurable)rdist.getImplementation()).config(cfg); // finish the job
      } catch (configException cfgex) {
        cfgex.printStackTrace(); // ??
      }
      
      return rdist;
  }

  //------------------------------------- CONFIGURABLE DISTRIBUTIONS
  
  class cfgBeta extends Beta implements Configurable {
    public cfgBeta(RandomStream randomGenerator) {
      super(0.,0.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String alpha,beta;
      if (null == (alpha = (String)cfg.findSingle("alpha")))
        throw new configException
          ("Beta distribution requires \"alpha\" attribute");
      if (null == (beta = (String)cfg.findSingle("beta")))
        throw new configException
          ("Beta distribution requires \"beta\" attribute");
      setState((new Double(alpha)).doubleValue(),
               (new Double(beta)).doubleValue());
    }
  }


  class cfgBinomial extends Binomial implements Configurable {
    public cfgBinomial(RandomStream randomGenerator) {
      super(1,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String p,n;
      if (null == (p = (String)cfg.findSingle("p")))
        throw new configException
          ("Binomial distribution requires \"p\" attribute");
      if (null == (n = (String)cfg.findSingle("n")))
        throw new configException
          ("Binomial distribution requires \"n\" attribute");
      setNandP((new Integer(n)).intValue(),
               (new Double(p)).doubleValue());
    }
  }


  class cfgBreitWigner extends BreitWigner implements Configurable {
    public cfgBreitWigner(RandomStream randomGenerator) {
      super(1.,0.2,1.0,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String mean, gamma, cut;
      if (null == (mean = (String)cfg.findSingle("mean")))
        throw new configException
          ("BreitWigner distribution requires \"mean\" attribute");
      if (null == (gamma = (String)cfg.findSingle("gamma")))
        throw new configException
          ("BreitWigner distribution requires \"gamma\" attribute");
      if (null == (cut = (String)cfg.findSingle("cut")))
        setState((new Double(mean)).doubleValue(),
                 (new Double(gamma)).doubleValue(),
                 Double.NEGATIVE_INFINITY);
      else 
        setState((new Double(mean)).doubleValue(),
                 (new Double(gamma)).doubleValue(),
                 (new Double(cut)).doubleValue());
    }
  }


  class cfgBreitWignerMeanSquare extends BreitWignerMeanSquare implements Configurable {
    public cfgBreitWignerMeanSquare(RandomStream randomGenerator) {
      super(1.,0.2,1.0,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String mean, gamma, cut;
      if (null == (mean = (String)cfg.findSingle("mean")))
        throw new configException
          ("BreitWignerMeanSquare distribution requires \"mean\" attribute");
      if (null == (gamma = (String)cfg.findSingle("gamma")))
        throw new configException
          ("BreitWignerMeanSquare distribution requires \"gamma\" attribute");
      if (null == (cut = (String)cfg.findSingle("cut")))
        setState((new Double(mean)).doubleValue(),
                 (new Double(gamma)).doubleValue(),
                 Double.NEGATIVE_INFINITY);
      else 
        setState((new Double(mean)).doubleValue(),
                 (new Double(gamma)).doubleValue(),
                 (new Double(cut)).doubleValue());
    }
  }


  class cfgChiSquare extends ChiSquare implements Configurable {
    public cfgChiSquare(RandomStream randomGenerator) {
      super(1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String freedom;
      if (null == (freedom = (String)cfg.findSingle("freedom")))
        throw new configException
          ("ChiSquare distribution requires \"freedom\" attribute");
      setState((new Double(freedom)).doubleValue());
    }
  }


  class cfgExponential extends Exponential implements Configurable {
    public cfgExponential(RandomStream randomGenerator) {
      super(1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String lambda;
      if (null == (lambda = (String)cfg.findSingle("lambda")))
        throw new configException
          ("Exponential distribution requires \"lambda\" attribute");
      setState((new Double(lambda)).doubleValue());
    }
  }



  class cfgGamma extends Gamma implements Configurable {
    public cfgGamma(RandomStream randomGenerator) {
      super(1.,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String alpha,lambda;
      if (null == (alpha = (String)cfg.findSingle("alpha")))
        throw new configException
          ("Gamma distribution requires \"alpha\" attribute");
      if (null == (lambda = (String)cfg.findSingle("lambda")))
        throw new configException
          ("Gamma distribution requires \"lambda\" attribute");
      setState((new Double(alpha)).doubleValue(),
               (new Double(lambda)).doubleValue());
    }
  }

  class cfgHyperGeometric extends HyperGeometric implements Configurable {
    public cfgHyperGeometric(RandomStream randomGenerator) {
      super(1,1,1,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String N,s,n;
      if (null == (N = (String)cfg.findSingle("N")))
        throw new configException
          ("HyperGeometric distribution requires \"N\" attribute");
      if (null == (s = (String)cfg.findSingle("s")))
        throw new configException
          ("HyperGeometric distribution requires \"s\" attribute");
      if (null == (n = (String)cfg.findSingle("n")))
        throw new configException
          ("HyperGeometric distribution requires \"n\" attribute");
      setState((new Integer(N)).intValue(),
               (new Integer(s)).intValue(),
               (new Integer(n)).intValue());
    }
  }

  class cfgHyperbolic extends Hyperbolic implements Configurable {
    public cfgHyperbolic(RandomStream randomGenerator) {
      super(10.,10.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String alpha,beta;
      if (null == (alpha = (String)cfg.findSingle("alpha")))
        throw new configException
          ("Hyperbolicdistribution requires \"alpha\" attribute");
      if (null == (beta = (String)cfg.findSingle("beta")))
        throw new configException
          ("Hyperbolic distribution requires \"beta\" attribute");
      setState((new Double(alpha)).doubleValue(),
               (new Double(beta)).doubleValue());
    }
  }

  class cfgLogarithmic extends Logarithmic implements Configurable {
    public cfgLogarithmic(RandomStream randomGenerator) {
      super(0.5,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String p;
      if (null == (p = (String)cfg.findSingle("p")))
        throw new configException
          ("Logarithmic distribution requires \"p\" attribute");
      setState((new Double(p)).doubleValue());
    }
  }

  class cfgNegativeBinomial extends NegativeBinomial implements Configurable {
    public cfgNegativeBinomial(RandomStream randomGenerator) {
      super(1,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String p,n;
      if (null == (p = (String)cfg.findSingle("p")))
        throw new configException
          ("NegativeBinomial distribution requires \"p\" attribute");
      if (null == (n = (String)cfg.findSingle("n")))
        throw new configException
          ("NegativeBinomial distribution requires \"n\" attribute");
      int nn = (new Integer(n)).intValue();
      setNandP(nn,(new Double(p)).doubleValue());
      this.gamma = new Gamma(nn,1.0,randomGenerator);
    }
  }


  class cfgNormal extends Normal implements Configurable {
    public cfgNormal(RandomStream randomGenerator) {
      super(0.,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String mean,standardDeviation;
      if (null == (mean = (String)cfg.findSingle("mean")))
        throw new configException
          ("Normal distribution requires \"mean\" attribute");
      if (null == (standardDeviation = (String)cfg.findSingle("standardDeviation")))
        throw new configException
          ("Normal distribution requires \"standardDeviation\" attribute");
      setState((new Double(mean)).doubleValue(),
               (new Double(standardDeviation)).doubleValue());
    }
  }


  class cfgPareto extends Pareto implements Configurable {
    public cfgPareto(RandomStream randomGenerator) {
      super(1.0,1.5,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String k,alpha;
      if (null == (k = (String)cfg.findSingle("k")))
        throw new configException
          ("Pareto distribution requires the scale \"k\" attribute");
      if (null == (alpha = (String)cfg.findSingle("alpha")))
        throw new configException
          ("Pareto distribution requires the shape \"alpha\" attribute");
      double kk = (new Double(k)).doubleValue();
      double aalpha = (new Double(alpha)).doubleValue();
      scale = kk;
      shape = aalpha;
      pwr = -1.0/aalpha;
    }
  }

// added 10/25/00 ato
  class cfgParetoII extends ParetoII implements Configurable {
    public cfgParetoII(RandomStream randomGenerator) {
      super(1.0,1.5,1.0,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String k,alpha, mu;
      if (null == (k = (String)cfg.findSingle("k")))
        throw new configException
          ("ParetoII distribution requires the scale \"k\" attribute");
      if (null == (alpha = (String)cfg.findSingle("alpha")))
        throw new configException
          ("ParetoII distribution requires the shape \"alpha\" attribute");
      if (null == (mu = (String)cfg.findSingle("mu")))
        throw new configException
          ("ParetoII distribution requires the shift \"mu\" attribute");
      double kk = (new Double(k)).doubleValue();
      double aalpha = (new Double(alpha)).doubleValue();
      double mmu = (new Double(mu)).doubleValue();
      scale = kk;
      shape = aalpha;
      pwr = -1.0/aalpha;
      shift = mmu;
    }
  }

  class cfgPoisson extends Poisson implements Configurable {
    public cfgPoisson(RandomStream randomGenerator) {
      super(0.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String mean;
      if (null == (mean = (String)cfg.findSingle("mean")))
        throw new configException
          ("Poisson distribution requires \"mean\" attribute");
      setMean((new Double(mean)).doubleValue());
    }
  }

  class cfgPoissonSlow extends PoissonSlow implements Configurable {
    public cfgPoissonSlow(RandomStream randomGenerator) {
      super(0.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String mean;
      if (null == (mean = (String)cfg.findSingle("mean")))
        throw new configException
          ("PoissonSlow distribution requires \"mean\" attribute");
      setMean((new Double(mean)).doubleValue());
    }
  }


  class cfgUniform extends Uniform implements Configurable {
    public cfgUniform(RandomStream randomGenerator) {
      super(0.,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String min,max;
      if (null == (min = (String)cfg.findSingle("min")))
        throw new configException
          ("Uniform distribution requires \"min\" attribute");
      if (null == (max = (String)cfg.findSingle("max")))
        throw new configException
          ("Uniform distribution requires \"max\" attribute");
      setState((new Double(min)).doubleValue(),
               (new Double(max)).doubleValue());
    }
  }

  class cfgVonMises extends VonMises implements Configurable {
    public cfgVonMises(RandomStream randomGenerator) {
      super(1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String freedom;
      if (null == (freedom = (String)cfg.findSingle("freedom")))
        throw new configException
          ("VonMises distribution requires \"freedom\" attribute");
      setState((new Double(freedom)).doubleValue());
    }
  }


  class cfgZeta extends Zeta implements Configurable {
    public cfgZeta(RandomStream randomGenerator) {
      super(1.,1.,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String ro,pk;
      if (null == (ro = (String)cfg.findSingle("ro")))
        throw new configException
          ("Zeta distribution requires \"ro\" attribute");
      if (null == (pk = (String)cfg.findSingle("pk")))
        throw new configException
          ("Zeta distribution requires \"pk\" attribute");
      setState((new Double(ro)).doubleValue(),
               (new Double(pk)).doubleValue());
    }
  }

  // added 10/25/00 ato
  class cfgWald extends Wald implements Configurable {
    public cfgWald(RandomStream randomGenerator) {
      super(1.0,1.0,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String s_lambda, s_mu;
      if (null == (s_lambda = (String)cfg.findSingle("lambda")))
        throw new configException
          ("Wald distribution requires the \"lambda\" attribute");
      if (null == (s_mu = (String)cfg.findSingle("mu")))
        throw new configException
          ("Wald distribution requires the \"mu\" attribute");
      double lambda = (new Double(s_lambda)).doubleValue();
      double mmu = (new Double(s_mu)).doubleValue();

      lam = lambda;
      mu = mmu;
    }
  }

  // added 10/25/00 ato
  class cfgWeibull extends Weibull implements Configurable {
    public cfgWeibull(RandomStream randomGenerator) {
      super(1.0,1.5,randomGenerator);
    }
    public void config(Configuration cfg) throws configException {
      String s_scale,s_shape;
      if (null == (s_scale = (String)cfg.findSingle("scale")))
        throw new configException
          ("Weibull distribution requires the \"scale\" attribute");
      if (null == (s_shape = (String)cfg.findSingle("shape")))
        throw new configException
          ("Weibull distribution requires the \"shape\" attribute");
      double sscale = (new Double(s_scale)).doubleValue();
      double sshape = (new Double(s_shape)).doubleValue();
      scale = sscale;
      shape = sshape;
    }
  }

}

